package ci.web.core;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedFile;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

import ci.web.util.MimeUtil;

import com.alibaba.fastjson.JSON;

@SuppressWarnings("deprecation")
class CiResponseImp implements CiResponse {

    protected ChannelHandlerContext context;
    protected FullHttpRequest request;
    protected DefaultHttpResponse response;
    private long contentLength = 0;
    private ByteBuf body = null;
    private Object fileInfo = null;
    protected HashSet<Cookie> cookies = null;
    
    public CiResponseImp(ChannelHandlerContext ctx, FullHttpRequest req) {
        this.request = req;
        this.context = ctx;
        this.response = new DefaultHttpResponse(req.protocolVersion(), HttpResponseStatus.OK);
        setHeader(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
    }

    protected boolean isKeepAlive(){
        return HttpUtil.isKeepAlive(request);
    }

    @Override
    public void setStatus(HttpResponseStatus status) {
        response.setStatus(status);
    }
    @Override
    public void setStatus(int statusCode, String statusInfo) {
        response.setStatus(new HttpResponseStatus(statusCode, statusInfo));
    }

    @Override
    public void setContentType(String contentType) {
        setHeader(HttpHeaderNames.CONTENT_TYPE, contentType);
    }

    @Override
    public void setHeader(CharSequence header, String value) {
        response.headers().set(header, value);
    }

    @Override
    public void setCookie(Cookie cookie) {
        if(cookies==null){
            cookies = new HashSet<Cookie>();
        }
        cookies.add(cookie);
    }
    @Override
    public void setCookie(String name, String value) {
        setCookie(name, value, Long.MIN_VALUE);
    }
    @Override
    public void setCookie(String name, String value, long maxAge) {
        setCookie(name, value, maxAge, "/", null, false, true);
    }
    @Override
    public void setCookie(String name, String value, long maxAge, String path, String domain, boolean secure, boolean httpOnly) {
        Cookie c = new DefaultCookie(name, value);
        c.setMaxAge(maxAge);
        c.setDomain(domain);
        c.setHttpOnly(httpOnly);
        c.setSecure(secure);
        c.setPath(path);
        setCookie(c);
    }
    @Override
    public void redirect(String location) {
        response.setStatus(HttpResponseStatus.MOVED_PERMANENTLY);
        setHeader(HttpHeaderNames.LOCATION, location);
        HttpUtil.setKeepAlive(response, false);
    }

    @Override
    public void redirect(String location, int httpStatusCode) {
        response.setStatus(new HttpResponseStatus(httpStatusCode, "rediret"));
        setHeader(HttpHeaderNames.LOCATION, location);
        HttpUtil.setKeepAlive(response, false);
    }

    @Override
    public void sendError(String errorInfo) {
        response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
        HttpUtil.setKeepAlive(response, false);
    }

    @Override
    public void sendError(int code, String errorInfo) {
        setStatus(code, errorInfo);
        HttpUtil.setKeepAlive(response, false);
    }

    @Override
    public void send(File file){
        this.send(file, 604800);
    }
    @Override
    public void send(File file, int fileCacheSecond) {
        try{
            long fileLength = file.length();
            contentLength += fileLength;
            
            RandomAccessFile raf = new RandomAccessFile(file, "r");
            setContentType(MimeUtil.getMimeType(file));
            setDateAndCacheHeaders(response, file, fileCacheSecond);
            
            if(context.pipeline().get(SslHandler.class)==null){
                fileInfo = new DefaultFileRegion(raf.getChannel(), 0L, fileLength);
            }else{
                fileInfo = new ChunkedFile(raf, 0L, fileLength, 8192);
            }
        }catch(IOException e){
            throw new RuntimeException(e);
        }
    }
    private static void setDateAndCacheHeaders(HttpResponse response, File fileToCache, int fileCacheSecond) {
        // Date header
        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
        format.setTimeZone(TimeZone.getTimeZone("GMT"));
        
        Date date = new GregorianCalendar().getTime();
        HttpHeaders.addDateHeader(response, HttpHeaderNames.DATE, date);
        HttpHeaders.addDateHeader(response, HttpHeaderNames.EXPIRES, date);
        HttpHeaders.addDateHeader(response, HttpHeaderNames.LAST_MODIFIED, new Date(fileToCache.lastModified()));
        HttpHeaders.addHeader(response, HttpHeaderNames.CACHE_CONTROL, "private, max-age=" + fileCacheSecond);
    }   

    @Override
    public void send(JSON info) {
        setHeader(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
        sendBytes(JSON.toJSONBytes(info));
    }
    @Override
    public void send(CharSequence body) {
        sendBytes(body.toString().getBytes());
    }
    @Override
    public void send(byte[] body) {
        setHeader(HttpHeaderNames.CONTENT_TYPE, "application/octet-stream");
        sendBytes(body);
    }
    protected void sendBytes(byte[] bin) {
        contentLength += bin.length;
        if(body==null){
            body = Unpooled.buffer(bin.length);
        }
        body.writeBytes(bin);
    }
    
    @Override
    public boolean isFinish(){
        return context==null;
    }
    @Override
    public synchronized void finish(){
        if(context==null){
            return;
        }
        HttpHeaders headers = response.headers();
        if(cookies!=null){
            List<String> arr = ServerCookieEncoder.STRICT.encode(cookies);
            for(String s:arr){
                headers.add(HttpHeaderNames.SET_COOKIE, s);
            }
        }
        if(headers.contains(HttpHeaderNames.CONTENT_TYPE)){
            String ctype = headers.get(HttpHeaderNames.CONTENT_TYPE);
            if(ctype.indexOf("charset")<0){
                ctype = ctype+"; charset=UTF-8";
                headers.set(HttpHeaderNames.CONTENT_TYPE, ctype);
            }
        }
        HttpUtil.setContentLength(response, contentLength);
        boolean close = !isKeepAlive();
        if(response.headers().contains(HttpHeaderNames.CONNECTION)){
            close = response.headers().get(HttpHeaderNames.CONNECTION).equalsIgnoreCase("close");
        }else{
            HttpUtil.setKeepAlive(response, !close);
        }
        context.write(response);
        if(body!=null){
            if(body.isReadable()){
                context.write(body);
            }else{
                body.release();
            }
        }
        if(fileInfo!=null){
            context.write(fileInfo, context.newProgressivePromise());
        }
        if(close){
            context.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT).addListener(ChannelFutureListener.CLOSE);
        }else{
            context.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        }
        context = null;
    }
}
